﻿/*******************************************************************************
 * You may amend and distribute as you like, but don't remove this header!
 *
 * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
 * See http://www.codeplex.com/EPPlus for details.
 *
 * Copyright (C) 2011  Jan Källman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU Lesser General Public License for more details.
 *
 * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
 * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
 *
 * All code and executables are provided "as is" with no warranty either express or implied. 
 * The author accepts no liability for any damage or loss of business that this product may cause.
 *
 * Code change notes:
 * 
 * Author							Change						Date
 * ******************************************************************************
 * Jan Källman		    Initial Release		        2010-01-28
 * Jan Källman		    License changed GPL-->LGPL  2011-12-27
 * Eyal Seagull		    Conditional Formatting      2012-04-03
 *******************************************************************************/
using lwzlz.Common.EPPlus.ConditionalFormatting;
using lwzlz.Common.EPPlus.DataValidation;
using lwzlz.Common.EPPlus.Style;
using lwzlz.Common.EPPlus.Style.XmlAccess;
using lwzlz.Common.EPPlus.Table;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace lwzlz.Common.EPPlus
{
    /// <summary>
    /// A range of cells 
    /// </summary>
    public class ExcelRangeBase : ExcelAddress, IExcelCell, IDisposable, IEnumerable<ExcelRangeBase>, IEnumerator<ExcelRangeBase>
    {
        /// <summary>
        /// Reference to the worksheet
        /// </summary>
        protected ExcelWorksheet _worksheet;
        private ExcelWorkbook _workbook = null;
        private delegate void _changeProp(_setValue method, object value);
        private delegate void _setValue(object value, int row, int col);
        private _changeProp _changePropMethod;
        private int _styleID;
        #region Constructors
        internal ExcelRangeBase(ExcelWorksheet xlWorksheet)
        {
            _worksheet = xlWorksheet;
            _ws = _worksheet.Name;
            this.AddressChange += new EventHandler(ExcelRangeBase_AddressChange);
            SetDelegate();
        }

        void ExcelRangeBase_AddressChange(object sender, EventArgs e)
        {
            SetDelegate();
        }
        internal ExcelRangeBase(ExcelWorksheet xlWorksheet, string address) :
            base(xlWorksheet == null ? "" : xlWorksheet.Name, address)
        {
            _worksheet = xlWorksheet;
            if (string.IsNullOrEmpty(_ws)) _ws = _worksheet == null ? "" : _worksheet.Name;
            this.AddressChange += new EventHandler(ExcelRangeBase_AddressChange);
            SetDelegate();
        }
        internal ExcelRangeBase(ExcelWorkbook wb, ExcelWorksheet xlWorksheet, string address, bool isName) :
            base(xlWorksheet == null ? "" : xlWorksheet.Name, address, isName)
        {
            _worksheet = xlWorksheet;
            _workbook = wb;
            if (string.IsNullOrEmpty(_ws)) _ws = (xlWorksheet == null ? null : xlWorksheet.Name);
            this.AddressChange += new EventHandler(ExcelRangeBase_AddressChange);
            SetDelegate();
        }
        ~ExcelRangeBase()
        {
            this.AddressChange -= new EventHandler(ExcelRangeBase_AddressChange);
        }
        #endregion
        #region Set Value Delegates		
        private void SetDelegate()
        {
            if (_fromRow == -1)
            {
                _changePropMethod = SetUnknown;
            }
            //Single cell
            else if (_fromRow == _toRow && _fromCol == _toCol && Addresses == null)
            {
                _changePropMethod = SetSingle;
            }
            //Range (ex A1:A2)
            else if (Addresses == null)
            {
                _changePropMethod = SetRange;
            }
            //Multi Range (ex A1:A2,C1:C2)
            else
            {
                _changePropMethod = SetMultiRange;
            }
        }
        /// <summary>
		/// We dont know the address yet. Set the delegate first time a property is set.
		/// </summary>
		/// <param name="valueMethod"></param>
		/// <param name="value"></param>
		private void SetUnknown(_setValue valueMethod, object value)
        {
            //Address is not set use, selected range
            if (_fromRow == -1)
            {
                SetToSelectedRange();
            }
            SetDelegate();
            _changePropMethod(valueMethod, value);
        }
        /// <summary>
        /// Set a single cell
        /// </summary>
        /// <param name="valueMethod"></param>
        /// <param name="value"></param>
        private void SetSingle(_setValue valueMethod, object value)
        {
            valueMethod(value, _fromRow, _fromCol);
        }
        /// <summary>
        /// Set a range
        /// </summary>
        /// <param name="valueMethod"></param>
        /// <param name="value"></param>
        private void SetRange(_setValue valueMethod, object value)
        {
            SetValueAddress(this, valueMethod, value);
        }
        /// <summary>
        /// Set a multirange (A1:A2,C1:C2)
        /// </summary>
        /// <param name="valueMethod"></param>
        /// <param name="value"></param>
        private void SetMultiRange(_setValue valueMethod, object value)
        {
            SetValueAddress(this, valueMethod, value);
            foreach (var address in Addresses)
            {
                SetValueAddress(address, valueMethod, value);
            }
        }
        /// <summary>
        /// Set the property for an address
        /// </summary>
        /// <param name="address"></param>
        /// <param name="valueMethod"></param>
        /// <param name="value"></param>
        private void SetValueAddress(ExcelAddress address, _setValue valueMethod, object value)
        {
            IsRangeValid("");
            if (_fromRow == 1 && _fromCol == 1 && _toRow == ExcelPackage.MaxRows && _toCol == ExcelPackage.MaxColumns)  //Full sheet (ex ws.Cells.Value=0). Set value for A1 only to avoid hanging 
            {
                throw (new ArgumentException("Can't reference all cells. Please use the indexer to set the range"));
            }
            else
            {
                for (int col = address.Start.Column; col <= address.End.Column; col++)
                {
                    for (int row = address.Start.Row; row <= address.End.Row; row++)
                    {
                        valueMethod(value, row, col);
                    }
                }
            }
        }
        #endregion
        #region Set property methods
        private void Set_StyleID(object value, int row, int col)
        {
            _worksheet.Cell(row, col).StyleID = (int)value;
        }
        private void Set_StyleName(object value, int row, int col)
        {
            _worksheet.Cell(row, col).SetNewStyleName(value.ToString(), _styleID);
        }
        private void Set_Value(object value, int row, int col)
        {
            ExcelCell c = _worksheet.Cell(row, col);
            if (c._sharedFormulaID > 0) SplitFormulas();
            _worksheet.Cell(row, col).Value = value;
        }
        private void Set_Formula(object value, int row, int col)
        {
            ExcelCell c = _worksheet.Cell(row, col);
            if (c._sharedFormulaID > 0) SplitFormulas();

            string formula = (value == null ? string.Empty : value.ToString());
            if (formula == string.Empty)
            {
                c.Formula = string.Empty;
            }
            else
            {
                if (formula[0] == '=') value = formula.Substring(1, formula.Length - 1); // remove any starting equalsign.
                c.Formula = formula;
            }
        }
        /// <summary>
        /// Handles shared formulas
        /// </summary>
        /// <param name="value">The  formula</param>
        /// <param name="address">The address of the formula</param>
        /// <param name="IsArray">If the forumla is an array formula.</param>
        private void Set_SharedFormula(string value, ExcelAddress address, bool IsArray)
        {
            if (_fromRow == 1 && _fromCol == 1 && _toRow == ExcelPackage.MaxRows && _toCol == ExcelPackage.MaxColumns)  //Full sheet (ex ws.Cells.Value=0). Set value for A1 only to avoid hanging 
            {
                throw (new InvalidOperationException("Can't set a formula for the entire worksheet"));
            }
            else if (address.Start.Row == address.End.Row && address.Start.Column == address.End.Column && !IsArray)             //is it really a shared formula? Arrayformulas can be one cell only
            {
                //Nope, single cell. Set the formula
                Set_Formula(value, address.Start.Row, address.Start.Column);
                return;
            }
            //RemoveFormuls(address);
            CheckAndSplitSharedFormula();
            ExcelWorksheet.Formulas f = new ExcelWorksheet.Formulas();
            f.Formula = value;
            f.Index = _worksheet.GetMaxShareFunctionIndex(IsArray);
            f.Address = address.FirstAddress;
            f.StartCol = address.Start.Column;
            f.StartRow = address.Start.Row;
            f.IsArray = IsArray;

            _worksheet._sharedFormulas.Add(f.Index, f);
            _worksheet.Cell(address.Start.Row, address.Start.Column).SharedFormulaID = f.Index;
            _worksheet.Cell(address.Start.Row, address.Start.Column).Formula = value;

            for (int col = address.Start.Column; col <= address.End.Column; col++)
            {
                for (int row = address.Start.Row; row <= address.End.Row; row++)
                {
                    _worksheet.Cell(row, col).SharedFormulaID = f.Index;
                }
            }
        }
        private void Set_HyperLink(object value, int row, int col)
        {
            _worksheet.Cell(row, col).Hyperlink = value as Uri;
        }
        private void Set_IsRichText(object value, int row, int col)
        {
            _worksheet.Cell(row, col).IsRichText = (bool)value;
        }
        private void Exists_Comment(object value, int row, int col)
        {
            ulong cellID = GetCellID(_worksheet.SheetID, row, col);
            if (_worksheet.Comments._comments.ContainsKey(cellID))
            {
                throw (new InvalidOperationException(string.Format("Cell {0} already contain a comment.", new ExcelCellAddress(row, col).Address)));
            }

        }
        private void Set_Comment(object value, int row, int col)
        {
            string[] v = (string[])value;
            Worksheet.Comments.Add(new ExcelRangeBase(_worksheet, GetAddress(_fromRow, _fromCol)), v[0], v[1]);
            //   _worksheet.Cell(row, col).Comment = comment;
        }
        #endregion
        private void SetToSelectedRange()
        {
            if (_worksheet.View.SelectedRange == "")
            {
                Address = "A1";
            }
            else
            {
                Address = _worksheet.View.SelectedRange;
            }
        }
        private void IsRangeValid(string type)
        {
            if (_fromRow <= 0)
            {
                if (_address == "")
                {
                    SetToSelectedRange();
                }
                else
                {
                    if (type == "")
                    {
                        throw (new InvalidOperationException(string.Format("Range is not valid for this operation: {0}", _address)));
                    }
                    else
                    {
                        throw (new InvalidOperationException(string.Format("Range is not valid for {0} : {1}", type, _address)));
                    }
                }
            }
        }
        #region Public Properties
        /// <summary>
        /// The styleobject for the range.
        /// </summary>
        public ExcelStyle Style
        {
            get
            {
                IsRangeValid("styling");
                return _worksheet.Workbook.Styles.GetStyleObject(_worksheet.Cell(_fromRow, _fromCol).StyleID, _worksheet.PositionID, Address);
            }
        }
        /// <summary>
        /// The named style
        /// </summary>
        public string StyleName
        {
            get
            {
                IsRangeValid("styling");
                return _worksheet.Cell(_fromRow, _fromCol).StyleName;
            }
            set
            {
                _styleID = _worksheet.Workbook.Styles.GetStyleIdFromName(value);
                if (_fromRow == 1 && _toRow == ExcelPackage.MaxRows)    //Full column
                {
                    ExcelColumn column;
                    //Get the startcolumn
                    ulong colID = ExcelColumn.GetColumnID(_worksheet.SheetID, _fromCol);
                    if (!_worksheet._columns.ContainsKey(colID))
                    {
                        column = _worksheet.Column(_fromCol);
                    }
                    else
                    {
                        column = _worksheet._columns[colID] as ExcelColumn;
                    }

                    var index = _worksheet._columns.IndexOf(colID);
                    while (column.ColumnMin <= _toCol)
                    {
                        if (column.ColumnMax > _toCol)
                        {
                            var newCol = _worksheet.CopyColumn(column, _toCol + 1, column.ColumnMax);
                            column.ColumnMax = _toCol;
                        }

                        column._styleName = value;
                        column._styleID = _styleID;

                        index++;
                        if (index >= _worksheet._columns.Count)
                        {
                            break;
                        }
                        else
                        {
                            column = (_worksheet._columns[index] as ExcelColumn);
                        }
                    }

                    if (column._columnMax < _toCol)
                    {
                        var newCol = _worksheet.Column(column._columnMax + 1) as ExcelColumn;
                        newCol._columnMax = _toCol;

                        newCol._styleID = _styleID;
                        newCol._styleName = value;
                    }
                    if (_fromCol == 1 && _toCol == ExcelPackage.MaxColumns) //FullRow
                    {
                        foreach (ExcelRow row in _worksheet._rows)
                        {
                            row._styleName = value;
                            row._styleId = _styleID;
                        }
                    }
                }
                else if (_fromCol == 1 && _toCol == ExcelPackage.MaxColumns) //FullRow
                {
                    for (int row = _fromRow; row <= _toRow; row++)
                    {
                        _worksheet.Row(row)._styleName = value;
                        _worksheet.Row(row)._styleId = _styleID;
                    }
                }

                if (!((_fromRow == 1 && _toRow == ExcelPackage.MaxRows) || (_fromCol == 1 && _toCol == ExcelPackage.MaxColumns))) //Cell specific
                {
                    for (int col = _fromCol; col <= _toCol; col++)
                    {
                        for (int row = _fromRow; row <= _toRow; row++)
                        {
                            _worksheet.Cell(row, col).StyleName = value;
                        }
                    }
                }
                else //Only set name on created cells. (uncreated cells is set on full row or full column).
                {
                    int tempIndex = _index;
                    var e = this as IEnumerator;
                    e.Reset();
                    while (e.MoveNext())
                    {
                        ((ExcelCell)_worksheet._cells[_index]).SetNewStyleName(value, _styleID);
                    }
                    _index = tempIndex;
                }
                //_changePropMethod(Set_StyleName, value);
            }
        }
        /// <summary>
        /// The style ID. 
        /// It is not recomended to use this one. Use Named styles as an alternative.
        /// If you do, make sure that you use the Style.UpdateXml() method to update any new styles added to the workbook.
        /// </summary>
        public int StyleID
        {
            get
            {
                return _worksheet.Cell(_fromRow, _fromCol).StyleID;
            }
            set
            {
                _changePropMethod(Set_StyleID, value);
            }
        }
        /// <summary>
        /// Set the range to a specific value
        /// </summary>
        public object Value
        {
            get
            {
                if (IsName)
                {
                    if (_worksheet == null)
                    {
                        return _workbook._names[_address].NameValue;
                    }
                    else
                    {
                        return _worksheet.Names[_address].NameValue; ;
                    }
                }
                else
                {
                    if (_fromRow == _toRow && _fromCol == _toCol)
                    {
                        return _worksheet.GetValue(_fromRow, _fromCol);
                    }
                    else
                    {
                        return GetValueArray();
                    }
                }
            }
            set
            {
                if (IsName)
                {
                    if (_worksheet == null)
                    {
                        _workbook._names[_address].NameValue = value;
                    }
                    else
                    {
                        _worksheet.Names[_address].NameValue = value;
                    }
                }
                else
                {
                    _changePropMethod(Set_Value, value);
                }
            }
        }

        private bool IsInfinityValue(object value)
        {
            double? valueAsDouble = value as double?;

            if (valueAsDouble.HasValue &&
                (double.IsNegativeInfinity(valueAsDouble.Value) || double.IsPositiveInfinity(valueAsDouble.Value)))
            {
                return true;
            }

            return false;
        }

        private object GetValueArray()
        {
            ExcelAddressBase addr;
            if (_fromRow == 1 && _fromCol == 1 && _toRow == ExcelPackage.MaxRows && _toCol == ExcelPackage.MaxColumns)
            {
                addr = _worksheet.Dimension;
                if (addr == null) return null;
            }
            else
            {
                addr = this;
            }
            object[,] v = new object[addr._toRow - addr._fromRow + 1, addr._toCol - addr._fromCol + 1];

            for (int col = addr._fromCol; col <= addr._toCol; col++)
            {
                for (int row = addr._fromRow; row <= addr._toRow; row++)
                {
                    if (_worksheet._cells.ContainsKey(GetCellID(_worksheet.SheetID, row, col)))
                    {
                        if (IsRichText)
                        {
                            v[row - addr._fromRow, col - addr._fromCol] = GetRichText(row, col).Text;
                        }
                        else
                        {
                            v[row - addr._fromRow, col - addr._fromCol] = _worksheet.Cell(row, col).Value;
                        }
                    }
                }
            }
            return v;
        }
        private ExcelAddressBase GetAddressDim(ExcelRangeBase addr)
        {
            int fromRow, fromCol, toRow, toCol;
            var d = _worksheet.Dimension;
            fromRow = addr._fromRow < d._fromRow ? d._fromRow : addr._fromRow;
            fromCol = addr._fromCol < d._fromCol ? d._fromCol : addr._fromCol;

            toRow = addr._toRow > d._toRow ? d._toRow : addr._toRow;
            toCol = addr._toCol > d._toCol ? d._toCol : addr._toCol;

            if (addr._fromCol == fromRow && addr._fromCol == addr._fromCol && addr._toRow == toRow && addr._toCol == _toCol)
            {
                return addr;
            }
            else
            {
                if (_fromRow > _toRow || _fromCol > _toCol)
                {
                    return null;
                }
                else
                {
                    return new ExcelAddressBase(fromRow, fromCol, toRow, toCol);
                }
            }
        }

        private object GetSingleValue()
        {
            if (IsRichText)
            {
                return RichText.Text;
            }
            else
            {
                return _worksheet.Cell(_fromRow, _fromCol).Value;
            }
        }
        /// <summary>
        /// Returns the formatted value.
        /// </summary>
        public string Text
        {
            get
            {
                return GetFormatedText(false);
            }
        }
        /// <summary>
        /// Set the column width from the content of the range. The minimum width is the value of the ExcelWorksheet.defaultColumnWidth property.
        /// Note: Cells containing formulas are ignored since EPPlus don't have a calculation engine.
        /// Wrapped and merged cells are also ignored.
        /// </summary>
        public void AutoFitColumns()
        {
            AutoFitColumns(_worksheet.DefaultColWidth);
        }

        /// <summary>
        /// Set the column width from the content of the range.
        /// Note: Cells containing formulas are ignored since EPPlus don't have a calculation engine.
        ///       Wrapped and merged cells are also ignored.
        /// </summary>
        /// <remarks>This method will not work if you run in an environment that does not support GDI</remarks>
        /// <param name="MinimumWidth">Minimum column width</param>
        public void AutoFitColumns(double MinimumWidth)
        {
            AutoFitColumns(MinimumWidth, double.MaxValue);
        }

        /// <summary>
        /// Set the column width from the content of the range.
        /// Note: Cells containing formulas are ignored since EPPlus don't have a calculation engine.
        ///       Wrapped and merged cells are also ignored.
        /// </summary>
        /// <param name="MinimumWidth">Minimum column width</param>
        /// <param name="MaximumWidth">Maximum column width</param>
        public void AutoFitColumns(double MinimumWidth, double MaximumWidth)
        {
            if (_fromCol < 1 || _fromRow < 1)
            {
                SetToSelectedRange();
            }
            Dictionary<int, Font> fontCache = new Dictionary<int, Font>();
            Font f;

            bool doAdjust = _worksheet._package.DoAdjustDrawings;
            _worksheet._package.DoAdjustDrawings = false;
            var drawWidths = _worksheet.Drawings.GetDrawingWidths();

            int fromCol = _fromCol > _worksheet.Dimension._fromCol ? _fromCol : _worksheet.Dimension._fromCol;
            int toCol = _toCol < _worksheet.Dimension._toCol ? _toCol : _worksheet.Dimension._toCol;
            if (Addresses == null)
            {
                for (int col = fromCol; col <= toCol; col++)
                {
                    _worksheet.Column(col).Width = MinimumWidth;
                }
            }
            else
            {
                foreach (var addr in Addresses)
                {
                    fromCol = addr._fromCol > _worksheet.Dimension._fromCol ? addr._fromCol : _worksheet.Dimension._fromCol;
                    toCol = addr._toCol < _worksheet.Dimension._toCol ? addr._toCol : _worksheet.Dimension._toCol;
                    for (int col = fromCol; col <= toCol; col++)
                    {
                        _worksheet.Column(col).Width = MinimumWidth;
                    }
                }
            }

            //Get any autofilter to widen these columns
            List<ExcelAddressBase> afAddr = new List<ExcelAddressBase>();
            if (_worksheet.AutoFilterAddress != null)
            {
                afAddr.Add(new ExcelAddressBase(_worksheet.AutoFilterAddress._fromRow,
                                                                                _worksheet.AutoFilterAddress._fromCol,
                                                                                _worksheet.AutoFilterAddress._fromRow,
                                                                                _worksheet.AutoFilterAddress._toCol));
                afAddr[afAddr.Count - 1]._ws = WorkSheet;
            }
            foreach (var tbl in _worksheet.Tables)
            {
                if (tbl.AutoFilterAddress != null)
                {
                    afAddr.Add(new ExcelAddressBase(tbl.AutoFilterAddress._fromRow,
                                                                            tbl.AutoFilterAddress._fromCol,
                                                                            tbl.AutoFilterAddress._fromRow,
                                                                            tbl.AutoFilterAddress._toCol));
                    afAddr[afAddr.Count - 1]._ws = WorkSheet;
                }
            }

            var styles = _worksheet.Workbook.Styles;
            var nf = styles.Fonts[styles.CellXfs[0].FontId];
            FontStyle fs = FontStyle.Regular;
            if (nf.Bold) fs |= FontStyle.Bold;
            if (nf.UnderLine) fs |= FontStyle.Underline;
            if (nf.Italic) fs |= FontStyle.Italic;
            if (nf.Strike) fs |= FontStyle.Strikeout;
            var nfont = new Font(nf.Name, nf.Size, fs);

            using (Bitmap b = new Bitmap(1, 1))
            {
                using (Graphics g = Graphics.FromImage(b))
                {
                    float normalSize = (float)Math.Truncate(g.MeasureString("00", nfont).Width - g.MeasureString("0", nfont).Width);
                    g.PageUnit = GraphicsUnit.Pixel;
                    foreach (var cell in this)
                    {
                        if (cell.Merge == true || cell.Style.WrapText) continue;
                        var fntID = styles.CellXfs[cell.StyleID].FontId;
                        if (fontCache.ContainsKey(fntID))
                        {
                            f = fontCache[fntID];
                        }
                        else
                        {
                            var fnt = styles.Fonts[fntID];
                            fs = FontStyle.Regular;
                            if (fnt.Bold) fs |= FontStyle.Bold;
                            if (fnt.UnderLine) fs |= FontStyle.Underline;
                            if (fnt.Italic) fs |= FontStyle.Italic;
                            if (fnt.Strike) fs |= FontStyle.Strikeout;
                            f = new Font(fnt.Name, fnt.Size, fs);
                            fontCache.Add(fntID, f);
                        }

                        //Truncate(({pixels}-5)/{Maximum Digit Width} * 100+0.5)/100

                        var size = g.MeasureString(cell.TextForWidth, f);
                        double width;
                        double r = styles.CellXfs[cell.StyleID].TextRotation;
                        if (r <= 0)
                        {
                            width = (size.Width + 5) / normalSize;
                        }
                        else
                        {
                            r = (r <= 90 ? r : r - 90);
                            width = (((size.Width - size.Height) * Math.Abs(System.Math.Cos(System.Math.PI * r / 180.0)) + size.Height) + 5) / normalSize;
                        }

                        foreach (var a in afAddr)
                        {
                            if (a.Collide(cell) != eAddressCollition.No)
                            {
                                width += 2.25;
                                break;
                            }
                        }

                        if (width > _worksheet.Column(cell._fromCol).Width)
                        {
                            _worksheet.Column(cell._fromCol).Width = width > MaximumWidth ? MaximumWidth : width;
                        }
                    }
                }
            }
            _worksheet.Drawings.AdjustWidth(drawWidths);
            _worksheet._package.DoAdjustDrawings = doAdjust;
        }

        internal string TextForWidth
        {
            get
            {
                return GetFormatedText(true);
            }
        }
        private string GetFormatedText(bool forWidthCalc)
        {
            object v = Value;
            if (v == null) return "";
            var styles = Worksheet.Workbook.Styles;
            var nfID = styles.CellXfs[StyleID].NumberFormatId;
            ExcelNumberFormatXml.ExcelFormatTranslator nf = null;
            for (int i = 0; i < styles.NumberFormats.Count; i++)
            {
                if (nfID == styles.NumberFormats[i].NumFmtId)
                {
                    nf = styles.NumberFormats[i].FormatTranslator;
                    break;
                }
            }

            string format, textFormat;
            if (forWidthCalc)
            {
                format = nf.NetFormatForWidth;
                textFormat = nf.NetTextFormatForWidth;
            }
            else
            {
                format = nf.NetFormat;
                textFormat = nf.NetTextFormat;
            }

            if (v is decimal || v.GetType().IsPrimitive)
            {
                double d;
                try
                {
                    d = Convert.ToDouble(v);
                }
                catch
                {
                    return "";
                }

                if (nf.DataType == ExcelNumberFormatXml.eFormatType.Number)
                {
                    if (string.IsNullOrEmpty(nf.FractionFormat))
                    {
                        return d.ToString(format, nf.Culture);
                    }
                    else
                    {
                        return nf.FormatFraction(d);
                    }
                }
                else if (nf.DataType == ExcelNumberFormatXml.eFormatType.DateTime)
                {
                    var date = DateTime.FromOADate(d);
                    return date.ToString(format, nf.Culture);
                }
            }
            else if (v is DateTime)
            {
                if (nf.DataType == ExcelNumberFormatXml.eFormatType.DateTime)
                {
                    return ((DateTime)v).ToString(format, nf.Culture);
                }
                else
                {
                    double d = ((DateTime)v).ToOADate();
                    if (string.IsNullOrEmpty(nf.FractionFormat))
                    {
                        return d.ToString(format, nf.Culture);
                    }
                    else
                    {
                        return nf.FormatFraction(d);
                    }
                }
            }
            else if (v is TimeSpan)
            {
                if (nf.DataType == ExcelNumberFormatXml.eFormatType.DateTime)
                {
                    return new DateTime(((TimeSpan)v).Ticks).ToString(format, nf.Culture);
                }
                else
                {
                    double d = (new DateTime(((TimeSpan)v).Ticks)).ToOADate();
                    if (string.IsNullOrEmpty(nf.FractionFormat))
                    {
                        return d.ToString(format, nf.Culture);
                    }
                    else
                    {
                        return nf.FormatFraction(d);
                    }
                }
            }
            else
            {
                if (textFormat == "")
                {
                    return v.ToString();
                }
                else
                {
                    return string.Format(textFormat, v);
                }
            }
            return v.ToString();
        }
        /// <summary>
        /// Gets or sets a formula for a range.
        /// </summary>
        public string Formula
        {
            get
            {
                if (IsName)
                {
                    if (_worksheet == null)
                    {
                        return _workbook._names[_address].NameFormula;
                    }
                    else
                    {
                        return _worksheet.Names[_address].NameFormula;
                    }
                }
                else
                {
                    return _worksheet.Cell(_fromRow, _fromCol).Formula;
                }
            }
            set
            {
                if (IsName)
                {
                    if (_worksheet == null)
                    {
                        _workbook._names[_address].NameFormula = value;
                    }
                    else
                    {
                        _worksheet.Names[_address].NameFormula = value;
                    }
                }
                else
                {
                    if (_fromRow == _toRow && _fromCol == _toCol)
                    {
                        Set_Formula(value, _fromRow, _fromCol);
                    }
                    else
                    {
                        Set_SharedFormula(value, this, false);
                        if (Addresses != null)
                        {
                            foreach (var address in Addresses)
                            {
                                Set_SharedFormula(value, address, false);
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Gets or Set a formula in R1C1 format.
        /// </summary>
        public string FormulaR1C1
        {
            get
            {
                IsRangeValid("FormulaR1C1");
                return _worksheet.Cell(_fromRow, _fromCol).FormulaR1C1;
            }
            set
            {
                IsRangeValid("FormulaR1C1");
                if (value.Length > 0 && value[0] == '=') value = value.Substring(1, value.Length - 1); // remove any starting equalsign.

                if (Addresses == null)
                {
                    Set_SharedFormula(ExcelCell.TranslateFromR1C1(value, _fromRow, _fromCol), this, false);
                }
                else
                {
                    Set_SharedFormula(ExcelCell.TranslateFromR1C1(value, _fromRow, _fromCol), new ExcelAddress(FirstAddress), false);
                    foreach (var address in Addresses)
                    {
                        Set_SharedFormula(ExcelCell.TranslateFromR1C1(value, address.Start.Row, address.Start.Column), address, false);
                    }
                }
            }
        }
        /// <summary>
        /// Set the hyperlink property for a range of cells
        /// </summary>
        public Uri Hyperlink
        {
            get
            {
                IsRangeValid("formulaR1C1");
                return _worksheet.Cell(_fromRow, _fromCol).Hyperlink;
            }
            set
            {
                _changePropMethod(Set_HyperLink, value);
            }
        }
        /// <summary>
        /// If the cells in the range are merged.
        /// </summary>
        public bool Merge
        {
            get
            {
                IsRangeValid("merging");
                for (int col = _fromCol; col <= _toCol; col++)
                {
                    for (int row = _fromRow; row <= _toRow; row++)
                    {
                        if (!_worksheet.Cell(row, col).Merge)
                        {
                            return false;
                        }
                    }
                }
                return true;
            }
            set
            {
                IsRangeValid("merging");
                SetMerge(value, FirstAddress);
                if (Addresses != null)
                {
                    foreach (var address in Addresses)
                    {
                        SetMerge(value, address._address);
                    }
                }
            }
        }

        private void SetMerge(bool value, string address)
        {
            if (!value)
            {
                if (_worksheet.MergedCells.List.Contains(address))
                {
                    SetCellMerge(false, address);
                    _worksheet.MergedCells.List.Remove(address);
                }
                else if (!CheckMergeDiff(false, address))
                {
                    throw (new Exception("Range is not fully merged.Specify the exact range"));
                }
            }
            else
            {
                if (CheckMergeDiff(false, address))
                {
                    SetCellMerge(true, address);
                    _worksheet.MergedCells.List.Add(address);
                }
                else
                {
                    if (!_worksheet.MergedCells.List.Contains(address))
                    {
                        throw (new Exception("Cells are already merged"));
                    }
                }
            }
        }
        /// <summary>
        /// Set an autofilter for the range
        /// </summary>
        public bool AutoFilter
        {
            get
            {
                IsRangeValid("autofilter");
                ExcelAddressBase address = _worksheet.AutoFilterAddress;
                if (address == null) return false;
                if (_fromRow >= address.Start.Row
                        &&
                        _toRow <= address.End.Row
                        &&
                        _fromCol >= address.Start.Column
                        &&
                        _toCol <= address.End.Column)
                {
                    return true;
                }
                return false;
            }
            set
            {
                IsRangeValid("autofilter");
                _worksheet.AutoFilterAddress = this;
                if (_worksheet.Names.ContainsKey("_xlnm._FilterDatabase"))
                {
                    _worksheet.Names.Remove("_xlnm._FilterDatabase");
                }
                var result = _worksheet.Names.Add("_xlnm._FilterDatabase", this);
                result.IsNameHidden = true;
            }
        }
        /// <summary>
        /// If the value is in richtext format.
        /// </summary>
        public bool IsRichText
        {
            get
            {
                IsRangeValid("richtext");
                return _worksheet.Cell(_fromRow, _fromCol).IsRichText;
            }
            set
            {
                _changePropMethod(Set_IsRichText, value);
            }
        }
        /// <summary>
        /// Is the range a part of an Arrayformula
        /// </summary>
        public bool IsArrayFormula
        {
            get
            {
                IsRangeValid("arrayformulas");
                return _worksheet.Cell(_fromRow, _fromCol).IsArrayFormula;
            }
        }
        ExcelRichTextCollection _rtc = null;
        /// <summary>
        /// Cell value is richtext formated. 
        /// </summary>
        public ExcelRichTextCollection RichText
        {
            get
            {
                IsRangeValid("richtext");
                if (_rtc == null)
                {
                    _rtc = GetRichText(_fromRow, _fromCol);
                }
                return _rtc;
            }
        }

        private ExcelRichTextCollection GetRichText(int row, int col)
        {
            XmlDocument xml = new XmlDocument();
            var cell = _worksheet.Cell(row, col);
            if (cell.Value != null)
            {
                if (cell.IsRichText)
                {
                    XmlHelper.LoadXmlSafe(xml, "<d:si xmlns:d=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" >" + cell.Value.ToString() + "</d:si>");
                }
                else
                {
                    xml.LoadXml("<d:si xmlns:d=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" ><d:r><d:t>" + SecurityElement.Escape(cell.Value.ToString()) + "</d:t></d:r></d:si>");
                }
            }
            else
            {
                xml.LoadXml("<d:si xmlns:d=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" />");
            }
            var rtc = new ExcelRichTextCollection(_worksheet.NameSpaceManager, xml.SelectSingleNode("d:si", _worksheet.NameSpaceManager), this);
            if (rtc.Count == 1 && cell.IsRichText == false)
            {
                IsRichText = true;
                var fnt = cell.Style.Font;
                rtc[0].PreserveSpace = true;
                rtc[0].Bold = fnt.Bold;
                rtc[0].FontName = fnt.Name;
                rtc[0].Italic = fnt.Italic;
                rtc[0].Size = fnt.Size;
                rtc[0].UnderLine = fnt.UnderLine;

                int hex;
                if (fnt.Color.Rgb != "" && int.TryParse(fnt.Color.Rgb, NumberStyles.HexNumber, null, out hex))
                {
                    rtc[0].Color = Color.FromArgb(hex);
                }

            }
            return rtc;
        }
        /// <summary>
        /// returns the comment object of the first cell in the range
        /// </summary>
        public ExcelComment Comment
        {
            get
            {
                IsRangeValid("comments");
                ulong cellID = GetCellID(_worksheet.SheetID, _fromRow, _fromCol);
                if (_worksheet.Comments._comments.ContainsKey(cellID))
                {
                    return _worksheet._comments._comments[cellID] as ExcelComment;
                }
                return null;
            }
        }
        /// <summary>
        /// WorkSheet object 
        /// </summary>
        public ExcelWorksheet Worksheet
        {
            get
            {
                return _worksheet;
            }
        }
        /// <summary>
        /// Address including sheetname
        /// </summary>
        public string FullAddress
        {
            get
            {
                string fullAddress = GetFullAddress(_worksheet.Name, _address);
                if (Addresses != null)
                {
                    foreach (var a in Addresses)
                    {
                        fullAddress += "," + GetFullAddress(_worksheet.Name, a.Address); ;
                    }
                }
                return fullAddress;
            }
        }
        /// <summary>
        /// Address including sheetname
        /// </summary>
        public string FullAddressAbsolute
        {
            get
            {
                string wbwsRef = string.IsNullOrEmpty(base._wb) ? base._ws : "[" + base._wb.Replace("'", "''") + "]" + _ws;
                string fullAddress = GetFullAddress(wbwsRef, GetAddress(_fromRow, _fromCol, _toRow, _toCol, true));
                if (Addresses != null)
                {
                    foreach (var a in Addresses)
                    {
                        fullAddress += "," + GetFullAddress(wbwsRef, GetAddress(a.Start.Row, a.Start.Column, a.End.Row, a.End.Column, true)); ;
                    }
                }
                return fullAddress;
            }
        }
        /// <summary>
        /// Address including sheetname
        /// </summary>
        internal string FullAddressAbsoluteNoFullRowCol
        {
            get
            {
                string wbwsRef = string.IsNullOrEmpty(base._wb) ? base._ws : "[" + base._wb.Replace("'", "''") + "]" + _ws;
                string fullAddress = GetFullAddress(wbwsRef, GetAddress(_fromRow, _fromCol, _toRow, _toCol, true), false);
                if (Addresses != null)
                {
                    foreach (var a in Addresses)
                    {
                        fullAddress += "," + GetFullAddress(wbwsRef, GetAddress(a.Start.Row, a.Start.Column, a.End.Row, a.End.Column, true), false); ;
                    }
                }
                return fullAddress;
            }
        }
        #endregion
        #region Private Methods
        /// <summary>
        /// Check if the range is partly merged
        /// </summary>
        /// <param name="startValue">the starting value</param>
        /// <param name="address">the address</param>
        /// <returns></returns>
        private bool CheckMergeDiff(bool startValue, string address)
        {
            ExcelAddress a = new ExcelAddress(address);
            for (int col = a._fromCol; col <= a._toCol; col++)
            {
                for (int row = a._fromRow; row <= a._toRow; row++)
                {
                    if (_worksheet.Cell(row, col).Merge != startValue)
                    {
                        return false;
                    }
                }
            }
            return true;
        }
        /// <summary>
        /// Set the merge flag for the range
        /// </summary>
        /// <param name="value"></param>
        /// <param name="address"></param>
        internal void SetCellMerge(bool value, string address)
        {
            ExcelAddress a = new ExcelAddress(address);
            for (int col = a._fromCol; col <= a._toCol; col++)
            {
                for (int row = a._fromRow; row <= a._toRow; row++)
                {
                    _worksheet.Cell(row, col).Merge = value;
                }
            }
        }
        /// <summary>
        /// Set the value without altering the richtext property
        /// </summary>
        /// <param name="value">the value</param>
        internal void SetValueRichText(object value)
        {
            if (_fromRow == 1 && _fromCol == 1 && _toRow == ExcelPackage.MaxRows && _toCol == ExcelPackage.MaxColumns)  //Full sheet (ex ws.Cells.Value=0). Set value for A1 only to avoid hanging 
            {
                _worksheet.Cell(1, 1).SetValueRichText(value);
            }
            else
            {
                for (int col = _fromCol; col <= _toCol; col++)
                {
                    for (int row = _fromRow; row <= _toRow; row++)
                    {
                        _worksheet.Cell(row, col).SetValueRichText(value);
                    }
                }
            }
        }
        /// <summary>
        /// Removes a shared formula
        /// </summary>
        private void RemoveFormuls(ExcelAddress address)
        {
            List<int> removed = new List<int>();
            int fFromRow, fFromCol, fToRow, fToCol;
            foreach (int index in _worksheet._sharedFormulas.Keys)
            {
                ExcelWorksheet.Formulas f = _worksheet._sharedFormulas[index];
                ExcelCell.GetRowColFromAddress(f.Address, out fFromRow, out fFromCol, out fToRow, out fToCol);
                if (((fFromCol >= address.Start.Column && fFromCol <= address.End.Column) ||
                     (fToCol >= address.Start.Column && fToCol <= address.End.Column)) &&
                     ((fFromRow >= address.Start.Row && fFromRow <= address.End.Row) ||
                     (fToRow >= address.Start.Row && fToRow <= address.End.Row)))
                {
                    for (int col = fFromCol; col <= fToCol; col++)
                    {
                        for (int row = fFromRow; row <= fToRow; row++)
                        {
                            _worksheet.Cell(row, col).SharedFormulaID = int.MinValue;
                        }
                    }
                    removed.Add(index);
                }
            }
            foreach (int index in removed)
            {
                _worksheet._sharedFormulas.Remove(index);
            }
        }
        internal void SetSharedFormulaID(int id)
        {
            for (int col = _fromCol; col <= _toCol; col++)
            {
                for (int row = _fromRow; row <= _toRow; row++)
                {
                    _worksheet.Cell(row, col).SharedFormulaID = id;
                }
            }
        }
        private void CheckAndSplitSharedFormula()
        {
            for (int col = _fromCol; col <= _toCol; col++)
            {
                for (int row = _fromRow; row <= _toRow; row++)
                {
                    if (_worksheet.Cell(row, col).SharedFormulaID >= 0)
                    {
                        SplitFormulas();
                        return;
                    }
                }
            }
        }

        private void SplitFormulas()
        {
            List<int> formulas = new List<int>();
            for (int col = _fromCol; col <= _toCol; col++)
            {
                for (int row = _fromRow; row <= _toRow; row++)
                {
                    int id = _worksheet.Cell(row, col).SharedFormulaID;
                    if (id >= 0 && !formulas.Contains(id))
                    {
                        if (_worksheet._sharedFormulas[id].IsArray &&
                                Collide(_worksheet.Cells[_worksheet._sharedFormulas[id].Address]) == eAddressCollition.Partly) // If the formula is an array formula and its on inside the overwriting range throw an exception
                        {
                            throw (new Exception("Can not overwrite a part of an array-formula"));
                        }
                        formulas.Add(_worksheet.Cell(row, col).SharedFormulaID);
                    }
                }
            }

            foreach (int ix in formulas)
            {
                SplitFormula(ix);
            }
        }

        private void SplitFormula(int ix)
        {
            var f = _worksheet._sharedFormulas[ix];
            var fRange = _worksheet.Cells[f.Address];
            var collide = Collide(fRange);

            //The formula is inside the currenct range, remove it
            if (collide == eAddressCollition.Inside)
            {
                _worksheet._sharedFormulas.Remove(ix);
                fRange.SetSharedFormulaID(int.MinValue);
            }
            else if (collide == eAddressCollition.Partly)
            {
                //The formula partly collides with the current range
                bool fIsSet = false;
                string formulaR1C1 = fRange.FormulaR1C1;
                //Top Range
                if (fRange._fromRow < _fromRow)
                {
                    f.Address = ExcelCell.GetAddress(fRange._fromRow, fRange._fromCol, _fromRow - 1, fRange._toCol);
                    fIsSet = true;
                }
                //Left Range
                if (fRange._fromCol < _fromCol)
                {
                    if (fIsSet)
                    {
                        f = new ExcelWorksheet.Formulas();
                        f.Index = _worksheet.GetMaxShareFunctionIndex(false);
                        f.StartCol = fRange._fromCol;
                        f.IsArray = false;
                        _worksheet._sharedFormulas.Add(f.Index, f);
                    }
                    else
                    {
                        fIsSet = true;
                    }
                    if (fRange._fromRow < _fromRow)
                        f.StartRow = _fromRow;
                    else
                    {
                        f.StartRow = fRange._fromRow;
                    }
                    if (fRange._toRow < _toRow)
                    {
                        f.Address = ExcelCell.GetAddress(f.StartRow, f.StartCol,
                                fRange._toRow, _fromCol - 1);
                    }
                    else
                    {
                        f.Address = ExcelCell.GetAddress(f.StartRow, f.StartCol,
                             _toRow, _fromCol - 1);
                    }
                    f.Formula = TranslateFromR1C1(formulaR1C1, f.StartRow, f.StartCol);
                    _worksheet.Cells[f.Address].SetSharedFormulaID(f.Index);
                }
                //Right Range
                if (fRange._toCol > _toCol)
                {
                    if (fIsSet)
                    {
                        f = new ExcelWorksheet.Formulas();
                        f.Index = _worksheet.GetMaxShareFunctionIndex(false);
                        f.IsArray = false;
                        _worksheet._sharedFormulas.Add(f.Index, f);
                    }
                    else
                    {
                        fIsSet = true;
                    }
                    f.StartCol = _toCol + 1;
                    if (_fromRow < fRange._fromRow)
                        f.StartRow = fRange._fromRow;
                    else
                    {
                        f.StartRow = _fromRow;
                    }

                    if (fRange._toRow < _toRow)
                    {
                        f.Address = ExcelCell.GetAddress(f.StartRow, f.StartCol,
                                fRange._toRow, fRange._toCol);
                    }
                    else
                    {
                        f.Address = ExcelCell.GetAddress(f.StartRow, f.StartCol,
                                _toRow, fRange._toCol);
                    }
                    f.Formula = TranslateFromR1C1(formulaR1C1, f.StartRow, f.StartCol);
                    _worksheet.Cells[f.Address].SetSharedFormulaID(f.Index);
                }
                //Bottom Range
                if (fRange._toRow > _toRow)
                {
                    if (fIsSet)
                    {
                        f = new ExcelWorksheet.Formulas();
                        f.Index = _worksheet.GetMaxShareFunctionIndex(false);
                        f.IsArray = false;
                        _worksheet._sharedFormulas.Add(f.Index, f);
                    }

                    f.StartCol = fRange._fromCol;
                    f.StartRow = _toRow + 1;

                    f.Formula = TranslateFromR1C1(formulaR1C1, f.StartRow, f.StartCol);

                    f.Address = ExcelCell.GetAddress(f.StartRow, f.StartCol,
                            fRange._toRow, fRange._toCol);
                    _worksheet.Cells[f.Address].SetSharedFormulaID(f.Index);

                }
            }
        }
        private object ConvertData(ExcelTextFormat Format, string v, int col, bool isText)
        {
            if (isText && (Format.DataTypes == null || Format.DataTypes.Length < col)) return v;

            double d;
            DateTime dt;
            if (Format.DataTypes == null || Format.DataTypes.Length < col || Format.DataTypes[col] == eDataTypes.Unknown)
            {
                string v2 = v.EndsWith("%") ? v.Substring(0, v.Length - 1) : v;
                if (double.TryParse(v2, NumberStyles.Any, Format.Culture, out d))
                {
                    if (v2 == v)
                    {
                        return d;
                    }
                    else
                    {
                        return d / 100;
                    }
                }
                if (DateTime.TryParse(v, Format.Culture, DateTimeStyles.None, out dt))
                {
                    return dt;
                }
                else
                {
                    return v;
                }
            }
            else
            {
                switch (Format.DataTypes[col])
                {
                    case eDataTypes.Number:
                        if (double.TryParse(v, NumberStyles.Any, Format.Culture, out d))
                        {
                            return d;
                        }
                        else
                        {
                            return v;
                        }
                    case eDataTypes.DateTime:
                        if (DateTime.TryParse(v, Format.Culture, DateTimeStyles.None, out dt))
                        {
                            return dt;
                        }
                        else
                        {
                            return v;
                        }
                    case eDataTypes.Percent:
                        string v2 = v.EndsWith("%") ? v.Substring(0, v.Length - 1) : v;
                        if (double.TryParse(v2, NumberStyles.Any, Format.Culture, out d))
                        {
                            return d / 100;
                        }
                        else
                        {
                            return v;
                        }

                    default:
                        return v;

                }
            }
        }
        #endregion
        #region Public Methods
        #region ConditionalFormatting
        /// <summary>
        /// Conditional Formatting for this range.
        /// </summary>
        public IRangeConditionalFormatting ConditionalFormatting
        {
            get
            {
                return new RangeConditionalFormatting(_worksheet, new ExcelAddress(Address));
            }
        }
        #endregion
        #region DataValidation
        /// <summary>
        /// Data validation for this range.
        /// </summary>
        public IRangeDataValidation DataValidation
        {
            get
            {
                return new RangeDataValidation(_worksheet, Address);
            }
        }
        #endregion
        #region LoadFromDataTable
        /// <summary>
        /// Load the data from the datatable starting from the top left cell of the range
        /// </summary>
        /// <param name="Table">The datatable to load</param>
        /// <param name="PrintHeaders">Print the column caption property (if set) or the columnname property if not, on first row</param>
        /// <param name="TableStyle">The table style to apply to the data</param>
        /// <returns>The filled range</returns>
        public ExcelRangeBase LoadFromDataTable(DataTable Table, bool PrintHeaders, TableStyles TableStyle)
        {
            var r = LoadFromDataTable(Table, PrintHeaders);

            int rows = Table.Rows.Count + (PrintHeaders ? 1 : 0) - 1;
            if (rows >= 0 && Table.Columns.Count > 0)
            {
                var tbl = _worksheet.Tables.Add(new ExcelAddressBase(_fromRow, _fromCol, _fromRow + (rows == 0 ? 1 : rows), _fromCol + Table.Columns.Count - 1), Table.TableName);
                tbl.ShowHeader = PrintHeaders;
                tbl.TableStyle = TableStyle;
            }
            return r;
        }
        /// <summary>
        /// Load the data from the datatable starting from the top left cell of the range
        /// </summary>
        /// <param name="Table">The datatable to load</param>
        /// <param name="PrintHeaders">Print the caption property (if set) or the columnname property if not, on first row</param>
        /// <returns>The filled range</returns>
        public ExcelRangeBase LoadFromDataTable(DataTable Table, bool PrintHeaders)
        {
            if (Table == null)
            {
                throw (new ArgumentNullException("Table can't be null"));
            }

            int col = _fromCol, row = _fromRow;
            if (PrintHeaders)
            {
                foreach (DataColumn dc in Table.Columns)
                {
                    // If no caption is set, the ColumnName property is called implicitly.
                    _worksheet.Cell(row, col++).Value = dc.Caption;
                }
                row++;
                col = _fromCol;
            }
            foreach (DataRow dr in Table.Rows)
            {
                foreach (object value in dr.ItemArray)
                {
                    _worksheet.Cell(row, col++).Value = value;
                }
                row++;
                col = _fromCol;
            }
            return _worksheet.Cells[_fromRow, _fromCol, row - 1, Table.Columns.Count];
        }
        #endregion
        #region LoadFromArrays
        /// <summary>
        /// Loads data from the collection of arrays of objects into the range, starting from
        /// the top-left cell.
        /// </summary>
        /// <param name="Data">The data.</param>
        public ExcelRangeBase LoadFromArrays(IEnumerable<object[]> Data)
        {
            //thanx to Abdullin for the code contribution
            if (Data == null) throw new ArgumentNullException("data");

            int column = _fromCol, row = _fromRow;

            foreach (var rowData in Data)
            {
                column = _fromCol;
                foreach (var cellData in rowData)
                {
                    _worksheet.Cell(row, column).Value = cellData;
                    column += 1;
                }
                row += 1;
            }
            return _worksheet.Cells[_fromRow, _fromCol, row - 1, column - 1];
        }
        #endregion
        #region LoadFromCollection
        /// <summary>
        /// Load a collection into a the worksheet starting from the top left row of the range.
        /// </summary>
        /// <typeparam name="T">The datatype in the collection</typeparam>
        /// <param name="Collection">The collection to load</param>
        /// <returns>The filled range</returns>
        public ExcelRangeBase LoadFromCollection<T>(IEnumerable<T> Collection)
        {
            return LoadFromCollection<T>(Collection, false, TableStyles.None, BindingFlags.Public | BindingFlags.Instance, null);
        }
        /// <summary>
        /// Load a collection of T into the worksheet starting from the top left row of the range.
        /// Default option will load all public instance properties of T
        /// </summary>
        /// <typeparam name="T">The datatype in the collection</typeparam>
        /// <param name="Collection">The collection to load</param>
        /// <param name="PrintHeaders">Print the property names on the first row</param>
        /// <returns>The filled range</returns>
        public ExcelRangeBase LoadFromCollection<T>(IEnumerable<T> Collection, bool PrintHeaders)
        {
            return LoadFromCollection<T>(Collection, PrintHeaders, TableStyles.None, BindingFlags.Public | BindingFlags.Instance, null);
        }
        /// <summary>
        /// Load a collection of T into the worksheet starting from the top left row of the range.
        /// Default option will load all public instance properties of T
        /// </summary>
        /// <typeparam name="T">The datatype in the collection</typeparam>
        /// <param name="Collection">The collection to load</param>
        /// <param name="PrintHeaders">Print the property names on the first row</param>
        /// <param name="TableStyle">Will create a table with this style. If set to TableStyles.None no table will be created</param>
        /// <returns>The filled range</returns>
        public ExcelRangeBase LoadFromCollection<T>(IEnumerable<T> Collection, bool PrintHeaders, TableStyles TableStyle)
        {
            return LoadFromCollection<T>(Collection, PrintHeaders, TableStyle, BindingFlags.Public | BindingFlags.Instance, null);
        }
        /// <summary>
        /// Load a collection into the worksheet starting from the top left row of the range.
        /// </summary>
        /// <typeparam name="T">The datatype in the collection</typeparam>
        /// <param name="Collection">The collection to load</param>
        /// <param name="PrintHeaders">Print the property names on the first row. Any underscore in the property name will be converted to a space.</param>
        /// <param name="TableStyle">Will create a table with this style. If set to TableStyles.None no table will be created</param>
        /// <param name="memberFlags">Property flags to use</param>
        /// <param name="Members">The properties to output. Must be of type T</param>
        /// <returns>The filled range</returns>
        public ExcelRangeBase LoadFromCollection<T>(IEnumerable<T> Collection, bool PrintHeaders, TableStyles TableStyle, BindingFlags memberFlags, MemberInfo[] Members)
        {
            var type = typeof(T);
            if (Members == null)
            {
                Members = type.GetProperties(memberFlags);
            }
            else
            {
                foreach (var t in Members)
                {
                    if (t.DeclaringType != type)
                    {
                        throw (new Exception("Supplied properties in parameter Properties must be of the same type as T"));
                    }
                }
            }

            int col = _fromCol, row = _fromRow;
            if (Members.Length > 0 && PrintHeaders)
            {
                foreach (var t in Members)
                {
                    _worksheet.Cell(row, col++).Value = t.Name.Replace('_', ' ');
                }
                row++;
            }

            if (Members.Length == 0)
            {
                foreach (var item in Collection)
                {
                    _worksheet.Cells[row++, col].Value = item;
                }
            }
            else
            {
                foreach (var item in Collection)
                {
                    col = _fromCol;
                    if (item is string || item is decimal || item is DateTime || item.GetType().IsPrimitive)
                    {
                        _worksheet.Cells[row, col++].Value = item;
                    }
                    else
                    {
                        foreach (var t in Members)
                        {
                            if (t is PropertyInfo)
                            {
                                _worksheet.Cells[row, col++].Value = ((PropertyInfo)t).GetValue(item, null);
                            }
                            else if (t is FieldInfo)
                            {
                                _worksheet.Cells[row, col++].Value = ((FieldInfo)t).GetValue(item);
                            }
                            else if (t is MethodInfo)
                            {
                                _worksheet.Cells[row, col++].Value = ((MethodInfo)t).Invoke(item, null);
                            }
                        }
                    }
                    row++;
                }
            }

            var r = _worksheet.Cells[_fromRow, _fromCol, row - 1, col - 1];

            if (TableStyle != TableStyles.None)
            {
                var tbl = _worksheet.Tables.Add(r, "");
                tbl.ShowHeader = PrintHeaders;
                tbl.TableStyle = TableStyle;
            }
            return r;
        }
        #endregion
        #region LoadFromText
        /// <summary>
        /// Loads a CSV text into a range starting from the top left cell.
        /// Default settings is Comma separation
        /// </summary>
        /// <param name="Text">The Text</param>
        /// <returns>The range containing the data</returns>
        public ExcelRangeBase LoadFromText(string Text)
        {
            return LoadFromText(Text, new ExcelTextFormat());
        }
        /// <summary>
        /// Loads a CSV text into a range starting from the top left cell.
        /// </summary>
        /// <param name="Text">The Text</param>
        /// <param name="Format">Information how to load the text</param>
        /// <returns>The range containing the data</returns>
        public ExcelRangeBase LoadFromText(string Text, ExcelTextFormat Format)
        {
            if (Format == null) Format = new ExcelTextFormat();
            string[] lines = Regex.Split(Text, Format.EOL);
            int row = _fromRow;
            int col = _fromCol;
            int maxCol = col;
            int lineNo = 1;
            if (Text == "")
            {
                _worksheet.Cells[_fromRow, _fromCol].Value = "";
            }
            else
            {
                foreach (string line in lines)
                {
                    if (lineNo > Format.SkipLinesBeginning && lineNo <= lines.Length - Format.SkipLinesEnd)
                    {
                        col = _fromCol;
                        string v = "";
                        bool isText = false, isQualifier = false;
                        int QCount = 0;
                        foreach (char c in line)
                        {
                            if (Format.TextQualifier != 0 && c == Format.TextQualifier)
                            {
                                if (!isText && v != "")
                                {
                                    throw (new Exception(string.Format("Invalid Text Qualifier in line : {0}", line)));
                                }
                                isQualifier = !isQualifier;
                                QCount += 1;
                                isText = true;
                            }
                            else
                            {
                                if (QCount > 1 && !string.IsNullOrEmpty(v))
                                {
                                    v += new string(Format.TextQualifier, QCount / 2);
                                }
                                else if (QCount > 2 && string.IsNullOrEmpty(v))
                                {
                                    v += new string(Format.TextQualifier, (QCount - 1) / 2);
                                }

                                if (isQualifier)
                                {
                                    v += c;
                                }
                                else
                                {
                                    if (c == Format.Delimiter)
                                    {
                                        _worksheet.Cell(row, col).Value = ConvertData(Format, v, col - _fromCol, isText);
                                        v = "";
                                        isText = false;
                                        col++;
                                    }
                                    else
                                    {
                                        if (QCount % 2 == 1)
                                        {
                                            throw (new Exception(string.Format("Text delimiter is not closed in line : {0}", line)));
                                        }
                                        v += c;
                                    }
                                }
                                QCount = 0;
                            }
                        }
                        if (QCount > 1)
                        {
                            v += new string(Format.TextQualifier, QCount / 2);
                        }

                        _worksheet.Cell(row, col).Value = ConvertData(Format, v, col - _fromCol, isText);
                        if (col > maxCol) maxCol = col;
                        row++;
                    }
                    lineNo++;
                }
            }
            return _worksheet.Cells[_fromRow, _fromCol, row - 1, maxCol];
        }
        /// <summary>
        /// Loads a CSV text into a range starting from the top left cell.
        /// </summary>
        /// <param name="Text">The Text</param>
        /// <param name="Format">Information how to load the text</param>
        /// <param name="TableStyle">Create a table with this style</param>
        /// <param name="FirstRowIsHeader">Use the first row as header</param>
        /// <returns></returns>
        public ExcelRangeBase LoadFromText(string Text, ExcelTextFormat Format, TableStyles TableStyle, bool FirstRowIsHeader)
        {
            var r = LoadFromText(Text, Format);

            var tbl = _worksheet.Tables.Add(r, "");
            tbl.ShowHeader = FirstRowIsHeader;
            tbl.TableStyle = TableStyle;

            return r;
        }
        /// <summary>
        /// Loads a CSV file into a range starting from the top left cell.
        /// </summary>
        /// <param name="TextFile">The Textfile</param>
        /// <returns></returns>
        public ExcelRangeBase LoadFromText(FileInfo TextFile)
        {
            return LoadFromText(File.ReadAllText(TextFile.FullName, Encoding.ASCII));
        }
        /// <summary>
        /// Loads a CSV file into a range starting from the top left cell.
        /// </summary>
        /// <param name="TextFile">The Textfile</param>
        /// <param name="Format">Information how to load the text</param>
        /// <returns></returns>
        public ExcelRangeBase LoadFromText(FileInfo TextFile, ExcelTextFormat Format)
        {
            return LoadFromText(File.ReadAllText(TextFile.FullName, Format.Encoding), Format);
        }
        /// <summary>
        /// Loads a CSV file into a range starting from the top left cell.
        /// </summary>
        /// <param name="TextFile">The Textfile</param>
        /// <param name="Format">Information how to load the text</param>
        /// <param name="TableStyle">Create a table with this style</param>
        /// <param name="FirstRowIsHeader">Use the first row as header</param>
        /// <returns></returns>
        public ExcelRangeBase LoadFromText(FileInfo TextFile, ExcelTextFormat Format, TableStyles TableStyle, bool FirstRowIsHeader)
        {
            return LoadFromText(File.ReadAllText(TextFile.FullName, Format.Encoding), Format, TableStyle, FirstRowIsHeader);
        }
        #endregion
        #region GetValue
        /// <summary>
        /// Get the strongly typed value of the cell.
        /// </summary>
        /// <typeparam name="T">The type</typeparam>
        /// <returns>The value. If the value can't be converted to the specified type, the default value will be returned</returns>
        public T GetValue<T>()
        {
            return _worksheet.GetTypedValue<T>(Value);
        }
        #endregion
        /// <summary>
        /// Get a range with an offset from the top left cell.
        /// The new range has the same dimensions as the current range
        /// </summary>
        /// <param name="RowOffset">Row Offset</param>
        /// <param name="ColumnOffset">Column Offset</param>
        /// <returns></returns>
        public ExcelRangeBase Offset(int RowOffset, int ColumnOffset)
        {
            if (_fromRow + RowOffset < 1 || _fromCol + ColumnOffset < 1 || _fromRow + RowOffset > ExcelPackage.MaxRows || _fromCol + ColumnOffset > ExcelPackage.MaxColumns)
            {
                throw (new ArgumentOutOfRangeException("Offset value out of range"));
            }
            string address = GetAddress(_fromRow + RowOffset, _fromCol + ColumnOffset, _toRow + RowOffset, _toCol + ColumnOffset);
            return new ExcelRangeBase(_worksheet, address);
        }
        /// <summary>
        /// Get a range with an offset from the top left cell.
        /// </summary>
        /// <param name="RowOffset">Row Offset</param>
        /// <param name="ColumnOffset">Column Offset</param>
        /// <param name="NumberOfRows">Number of rows. Minimum 1</param>
        /// <param name="NumberOfColumns">Number of colums. Minimum 1</param>
        /// <returns></returns>
        public ExcelRangeBase Offset(int RowOffset, int ColumnOffset, int NumberOfRows, int NumberOfColumns)
        {
            if (NumberOfRows < 1 || NumberOfColumns < 1)
            {
                throw (new Exception("Number of rows/columns must be greater than 0"));
            }
            NumberOfRows--;
            NumberOfColumns--;
            if (_fromRow + RowOffset < 1 || _fromCol + ColumnOffset < 1 || _fromRow + RowOffset > ExcelPackage.MaxRows || _fromCol + ColumnOffset > ExcelPackage.MaxColumns ||
                 _fromRow + RowOffset + NumberOfRows < 1 || _fromCol + ColumnOffset + NumberOfColumns < 1 || _fromRow + RowOffset + NumberOfRows > ExcelPackage.MaxRows || _fromCol + ColumnOffset + NumberOfColumns > ExcelPackage.MaxColumns)
            {
                throw (new ArgumentOutOfRangeException("Offset value out of range"));
            }
            string address = GetAddress(_fromRow + RowOffset, _fromCol + ColumnOffset, _fromRow + RowOffset + NumberOfRows, _fromCol + ColumnOffset + NumberOfColumns);
            return new ExcelRangeBase(_worksheet, address);
        }
        /// <summary>
        /// Adds a new comment for the range.
        /// If this range contains more than one cell, the top left comment is returned by the method.
        /// </summary>
        /// <param name="Text"></param>
        /// <param name="Author"></param>
        /// <returns>A reference comment of the top left cell</returns>
        public ExcelComment AddComment(string Text, string Author)
        {
            //Check if any comments exists in the range and throw an exception
            _changePropMethod(Exists_Comment, null);
            //Create the comments
            _changePropMethod(Set_Comment, new string[] { Text, Author });


            return _worksheet.Comments[new ExcelCellAddress(_fromRow, _fromCol)];
        }

        /// <summary>
        /// Copies the range of cells to an other range
        /// </summary>
        /// <param name="Destination">The start cell where the range will be copied.</param>
        public void Copy(ExcelRangeBase Destination)
        {
            bool sameWorkbook = Destination._worksheet.Workbook == _worksheet.Workbook;
            ExcelStyles sourceStyles = _worksheet.Workbook.Styles,
                                    styles = Destination._worksheet.Workbook.Styles;
            Dictionary<int, int> styleCashe = new Dictionary<int, int>();

            //Delete all existing cells;            
            List<ExcelCell> newCells = new List<ExcelCell>();
            Dictionary<ulong, ExcelCell> mergedCells = new Dictionary<ulong, ExcelCell>();
            foreach (var cell in this)
            {
                //Clone the cell
                var copiedCell = (_worksheet._cells[GetCellID(_worksheet.SheetID, cell._fromRow, cell._fromCol)] as ExcelCell);

                var newCell = copiedCell.Clone(Destination._worksheet,
                        Destination._fromRow + (copiedCell.Row - _fromRow),
                        Destination._fromCol + (copiedCell.Column - _fromCol));

                newCell.MergeId = _worksheet.GetMergeCellId(copiedCell.Row, copiedCell.Column);

                //If the formula is shared, remove the shared ID and set the formula for the cell.
                if (newCell._sharedFormulaID >= 0)
                {
                    newCell._sharedFormulaID = int.MinValue;
                    newCell.Formula = cell.Formula;
                }

                if (!string.IsNullOrEmpty(newCell.Formula))
                {
                    newCell.Formula = ExcelCell.UpdateFormulaReferences(newCell.Formula, newCell.Row - copiedCell.Row, (newCell.Column - copiedCell.Column), 1, 1);
                }

                //If its not the same workbook we must copy the styles to the new workbook.
                if (!sameWorkbook)
                {
                    if (styleCashe.ContainsKey(cell.StyleID))
                    {
                        newCell.StyleID = styleCashe[cell.StyleID];
                    }
                    else
                    {
                        newCell.StyleID = styles.CloneStyle(sourceStyles, cell.StyleID);
                        styleCashe.Add(cell.StyleID, newCell.StyleID);
                    }
                }
                newCells.Add(newCell);
                if (newCell.Merge) mergedCells.Add(newCell.CellID, newCell);
            }

            //Now clear the destination.
            Destination.Offset(0, 0, (_toRow - _fromRow) + 1, (_toCol - _fromCol) + 1).Clear();

            //And last add the new cells to the worksheet
            foreach (var cell in newCells)
            {
                Destination.Worksheet._cells.Add(cell);
            }
            //Add merged cells
            if (mergedCells.Count > 0)
            {
                List<ExcelAddressBase> mergedAddresses = new List<ExcelAddressBase>();
                foreach (var cell in mergedCells.Values)
                {
                    if (!IsAdded(cell, mergedAddresses))
                    {
                        int startRow = cell.Row, startCol = cell.Column, endRow = cell.Row, endCol = cell.Column + 1;
                        while (mergedCells.ContainsKey(ExcelCell.GetCellID(Destination.Worksheet.SheetID, endRow, endCol)))
                        {
                            ExcelCell next = mergedCells[ExcelCell.GetCellID(Destination.Worksheet.SheetID, endRow, endCol)];
                            if (cell.MergeId != next.MergeId)
                            {
                                break;
                            }
                            endCol++;
                        }

                        while (IsMerged(mergedCells, Destination.Worksheet, endRow, startCol, endCol - 1, cell))
                        {
                            endRow++;
                        }

                        mergedAddresses.Add(new ExcelAddressBase(startRow, startCol, endRow - 1, endCol - 1));
                    }
                }
                Destination.Worksheet.MergedCells.List.AddRange((from r in mergedAddresses select r.Address));
            }
        }

        private bool IsAdded(ExcelCell cell, List<ExcelAddressBase> mergedAddresses)
        {
            foreach (var address in mergedAddresses)
            {
                if (address.Collide(new ExcelAddressBase(cell.CellAddress)) == eAddressCollition.Inside)
                {
                    return true;
                }
            }
            return false;
        }

        private bool IsMerged(Dictionary<ulong, ExcelCell> mergedCells, ExcelWorksheet worksheet, int row, int startCol, int endCol, ExcelCell cell)
        {
            for (int col = startCol; col <= endCol; col++)
            {
                if (!mergedCells.ContainsKey(ExcelCell.GetCellID(worksheet.SheetID, row, col)))
                {
                    return false;
                }
                else
                {
                    ExcelCell next = mergedCells[ExcelCell.GetCellID(worksheet.SheetID, row, col)];
                    if (cell.MergeId != next.MergeId)
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        /// <summary>
        /// Clear all cells
        /// </summary>
        public void Clear()
        {
            Delete(this);
        }
        /// <summary>
        /// Creates an array-formula.
        /// </summary>
        /// <param name="ArrayFormula">The formula</param>
        public void CreateArrayFormula(string ArrayFormula)
        {
            if (Addresses != null)
            {
                throw (new Exception("An Arrayformula can not have more than one address"));
            }
            Set_SharedFormula(ArrayFormula, this, true);
        }
        private void Delete(ExcelAddressBase Range)
        {
            DeleteCheckMergedCells(Range);
            //First find the start cell
            ulong startID = GetCellID(_worksheet.SheetID, Range._fromRow, Range._fromCol);
            int index = _worksheet._cells.IndexOf(startID);
            if (index < 0)
            {
                index = ~index;
            }
            ExcelCell cell;
            //int row=cell.Row, col=cell.Column;
            //Remove all cells in the range
            while (index < _worksheet._cells.Count)
            {
                cell = _worksheet._cells[index] as ExcelCell;
                if (cell.Row > Range._toRow || cell.Row == Range._toRow && cell.Column > Range._toCol)
                {
                    break;
                }
                else
                {
                    if (cell.Column >= Range._fromCol && cell.Column <= Range._toCol)
                    {
                        _worksheet._cells.Delete(cell.CellID);
                    }
                    else
                    {
                        index++;
                    }
                }
            }

            //Delete multi addresses as well
            if (Addresses != null)
            {
                foreach (var sub in Addresses)
                {
                    Delete(sub);
                }
            }
        }

        private void DeleteCheckMergedCells(ExcelAddressBase Range)
        {
            var removeItems = new List<string>();
            foreach (var addr in Worksheet.MergedCells)
            {
                var addrCol = Range.Collide(new ExcelAddress(Range.WorkSheet, addr));
                if (addrCol != eAddressCollition.No)
                {
                    if (addrCol == eAddressCollition.Inside)
                    {
                        removeItems.Add(addr);
                    }
                    else
                    {
                        throw (new InvalidOperationException("Can't remove/overwrite cells that are merged"));
                    }
                }
            }
            foreach (var item in removeItems)
            {
                Worksheet.MergedCells.Remove(item);
            }
        }
        #endregion
        #region IDisposable Members

        public void Dispose()
        {
            //_worksheet = null;
        }

        #endregion
        #region "Enumerator"
        int _index;
        ulong _toCellId;
        int _enumAddressIx;
        public IEnumerator<ExcelRangeBase> GetEnumerator()
        {
            Reset();
            return this;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            Reset();
            return this;
        }

        /// <summary>
        /// The current range when enumerating
        /// </summary>
        public ExcelRangeBase Current
        {
            get
            {
                return new ExcelRangeBase(_worksheet, (_worksheet._cells[_index] as ExcelCell).CellAddress);
            }
        }

        /// <summary>
        /// The current range when enumerating
        /// </summary>
        object IEnumerator.Current
        {
            get
            {
                return ((object)(new ExcelRangeBase(_worksheet, (_worksheet._cells[_index] as ExcelCell).CellAddress)));
            }
        }

        public bool MoveNext()
        {
            _index++;
            if (_enumAddressIx == -1)
            {
                GetNextIndexEnum(_fromRow, _fromCol, _toRow, _toCol);

                if (_index >= _worksheet._cells.Count || _worksheet._cells[_index].RangeID > _toCellId)
                {
                    if (Addresses == null)
                    {
                        return false;
                    }
                    else
                    {
                        _enumAddressIx++;
                        GetStartIndexEnum(Addresses[0].Start.Row, Addresses[0].Start.Column, Addresses[0].End.Row, Addresses[0].End.Column);
                        return MoveNext();
                    }
                }

            }
            else
            {
                GetNextIndexEnum(Addresses[_enumAddressIx].Start.Row, Addresses[_enumAddressIx].Start.Column, Addresses[_enumAddressIx].End.Row, Addresses[_enumAddressIx].End.Column);
                if (_index >= _worksheet._cells.Count || _worksheet._cells[_index].RangeID > _toCellId)
                {
                    if (++_enumAddressIx < Addresses.Count)
                    {
                        GetStartIndexEnum(Addresses[_enumAddressIx].Start.Row, Addresses[_enumAddressIx].Start.Column, Addresses[_enumAddressIx].End.Row, Addresses[_enumAddressIx].End.Column);
                        MoveNext();
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        public void Reset()
        {
            _enumAddressIx = -1;
            GetStartIndexEnum(_fromRow, _fromCol, _toRow, _toCol);
        }

        private void GetNextIndexEnum(int fromRow, int fromCol, int toRow, int toCol)
        {
            if (_index >= _worksheet._cells.Count) return;
            ExcelCell cell = _worksheet._cells[_index] as ExcelCell;
            while (cell.Column > toCol || cell.Column < fromCol)
            {
                if (cell.Column < fromCol)
                {
                    _index = _worksheet._cells.IndexOf(ExcelAddress.GetCellID(_worksheet.SheetID, cell.Row, fromCol));
                }
                else
                {
                    _index = _worksheet._cells.IndexOf(ExcelAddress.GetCellID(_worksheet.SheetID, cell.Row + 1, fromCol));
                }

                if (_index < 0)
                {
                    _index = ~_index;
                }
                if (_index >= _worksheet._cells.Count || _worksheet._cells[_index].RangeID > _toCellId)
                {
                    break;
                }
                cell = _worksheet._cells[_index] as ExcelCell;
            }
        }

        private void GetStartIndexEnum(int fromRow, int fromCol, int toRow, int toCol)
        {
            _index = _worksheet._cells.IndexOf(ExcelCellBase.GetCellID(_worksheet.SheetID, fromRow, fromCol));
            _toCellId = ExcelCellBase.GetCellID(_worksheet.SheetID, toRow, toCol);
            if (_index < 0)
            {
                _index = ~_index;
            }
            _index--;
        }
        #endregion
    }
}
